package cn.xyfmzmy.timingwheel;

import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.LockSupport;

/**
 * 手写时间轮
 */
public class TimerWheel {

    private volatile long startTime;     // 时间轮启动时间
    private final MpscTaskQueue[] wheel;       // 时间轮
    private final Ticker ticker;      // 触发线程
    private final AtomicBoolean isStart;  // 记录时间轮是否启动
    private final CountDownLatch startTimeLatch;    // 用于帮助判断startTime是否被赋值
    private final ExecutorService executor;      // 延迟任务的执行器

    public TimerWheel() {
//        this.wheel = new Slot[10];
        this.wheel = new MpscTaskQueue[10];
        this.ticker = new Ticker();
        this.isStart = new AtomicBoolean(false);
        this.startTimeLatch = new CountDownLatch(1);
        this.executor = Executors.newFixedThreadPool(6);
        for (int i = 0; i < wheel.length; i++) {
            wheel[i] = new MpscTaskQueue();
        }
    }

    // 添加延迟任务
    public void addDelayTask(Runnable runnable, long delayMs) {
        // 确保：1.启动时间轮 2.startTime被赋值了
        start();
        // 创建延迟任务
        DelayTask delayTask = new DelayTask(runnable, delayMs);
        // 添加到对应的槽中
        int index = Math.toIntExact((delayTask.deadline - startTime) / 100 % wheel.length);
        MpscTaskQueue slot = wheel[index];
        slot.pushTask(delayTask);
    }

    // 启动时间轮
    private void start() {
        // cas原子变量，确保同时添加多个任务时只会启动一次
        if (isStart.compareAndSet(false, true)) {
            ticker.start();
        }
        // 等待startTime被赋值（startTime赋值完成后执行countDown方法，计数由1变成0，await就不会阻塞）
        try {
            startTimeLatch.await();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 停止时间轮
    public void stop() {
        if (isStart.compareAndSet(true, false)) {
            // 唤醒阻塞线程
            LockSupport.unpark(ticker);
        }
    }


    // 延迟任务
    public static class DelayTask {
        final Runnable runnable;        // 具体的任务
        final long deadline;            // 执行时间
        DelayTask next;
        DelayTask pre;

        public DelayTask(Runnable runnable, long delayMs) {
            this.runnable = runnable;
            this.deadline = System.currentTimeMillis() + delayMs;
        }
    }

    // 时间槽
    public class Slot {

        // 每个槽存储一个链表(这里用双向链表)
        DelayTask head;
        DelayTask tail;

        // 执行该槽里应当执行的任务
        public synchronized void runWithDeadline(long tickTime) {
            DelayTask current = head;
            while (current != null) {
                // 执行当前时间范围内的任务
                if (current.deadline <= tickTime) {
                    removeTask(current);
                    // 提交任务到线程池(执行器)执行，将触发器与执行器分开，避免任务时间过长影响时间轮的触发
                    executor.execute(current.runnable);
                }
                current = current.next;
            }
        }

        private void removeTask(DelayTask current) {
            if (current.pre != null) {
                current.pre.next = current.next;
            }
            if (current.next != null) {
                current.next.pre = current.pre;
            }
            if (current == head) {
                head = current.next;
            }
            if (current == tail) {
                tail = current.pre;
            }
            current.pre = null;
            current.next = null;
        }

        public synchronized void pushDelayTask(DelayTask delayTask) {
            if (head == null) {
                head = tail = delayTask;
            } else {
                tail.next = delayTask;
                delayTask.pre = tail;
                tail = delayTask;
            }
        }
    }

    // 触发线程
    public class Ticker extends Thread {
        int tickCount = 0;      // 触发次数

        @Override
        public void run() {
            startTime = System.currentTimeMillis();         // 记录时间轮启动时间
            startTimeLatch.countDown();                     // 停止对startTime赋值的等待
            while (isStart.get()) {
                // 计算触发时间，每100ms执行一次
                long tickTime = startTime + (tickCount + 1) * 100L;
                // 阻塞至触发时间
                while (System.currentTimeMillis() <= tickTime) {
                    LockSupport.parkUntil(tickTime);
                    // 如果等待过程中时间轮被停止了，就直接返回，不往下执行
                    if (!isStart.get()) {
                        return;
                    }
                }
                // 获取触发时间对应的插槽，执行插槽中的延迟任务
                int index = tickCount % wheel.length;
                MpscTaskQueue slot = wheel[index];
                List<Runnable> taskList = slot.removeAndReturnShouldRun(tickTime);
                // 使用执行器来执行，避免任务时间过长影响时间轮的触发
                taskList.forEach(executor::execute);
                tickCount++;
            }
        }
    }

}
